/*
 * SFTPTransportSocket.h
 *
 *  Created on: Mar 15, 2011
 *      Author: sms103
 */

#ifndef SFTPTRANSPORTSOCKET_H_
#define SFTPTRANSPORTSOCKET_H_

class  SFTPTransportSocket;

#include "TCPSocket.h"
#include "SFTPMessage.h"
#include "EmulatedNetworkSocket.h"
#include "../shared/ParamParser.h"
#include <pthread.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <time.h>
#include <limits.h>
#include <string.h>
#include <assert.h>

#include "CRC32.h"

#include <queue>
#include <list>
#include <string>
#include <sstream>

//bytes
#define DEFAULT_TRANSPORT_MSS 15000
//bytes, 1MB
#define DEFAULT_TRANSPORT_SEND_BUFFER_SIZE 1000000

//bytes, 1MB
#define DEFAULT_TRANSPORT_RECEIVE_BUFFER_SIZE 1000000

//timeout in milliseconds
#define DEFAULT_TRANSPORT_TIMEOUT 500

//the connection is considered lost after this many milliseconds
//waiting for a response from the other side
#define DEFAULT_CONN_CLOSED_TIMEOUT 12000

//retransmission protocol options
#define GO_BACK_N 0
#define SELECTIVE_REPEAT 1

#define DEFAULT_RETRANSMISSION_PROTOCOL GO_BACK_N

//event types
#define APP_DATA 1
#define NETWORK_DATA 2
#define APP_CLOSE_REQUESTED 4
#define RESEND_DATA 8


struct transport_stats{
	uint32_t errorsDetected;
};


enum ConnectionState{
	ESTABLISHED,
	SYN_SENT,
	SYN_RCVD,
	FIN_WAIT_1,
	FIN_WAIT_2,
	TIME_WAIT,
	CLOSE_WAIT,
	LAST_ACK,
	CLOSED
};

struct queue_entry{
	void *packet;
	size_t packetLen;
	struct timeval timestamp;
	uint16_t resendCount;
};



typedef uint32_t tcp_seq;

typedef struct stcphdr
{
    tcp_seq  th_seq;    /* sequence number */
    tcp_seq  th_ack;    /* acknowledgement number */
    uint32_t gap_length; /* Used only with selective repeat, and with a SYN
     	 	 	 	 	 	 packet to specify MSS*/
    uint8_t  th_flags;
#define TH_FIN  0x01
#define TH_SYN  0x02
#define TH_ACK  0x04
#define TH_GBN  0x08 //go back n
#define TH_SRP  0x10 //selective repeat
    uint8_t th_misc;//for alignment
    uint32_t th_sum;    /* 32bit crc code */
    uint32_t th_win;    /* the sequence number of the right edge of the current window, i.e. the
     	 	 	 	 	 	 highest byte that will be accepted*/

} __attribute__ ((packed)) STCPHeader;



#ifndef MIN
    #define MIN(x,y)  ((x) <= (y) ? (x) : (y))
#endif

#ifndef MAX
    #define MAX(x,y)  ((x) >= (y) ? (x) : (y))
#endif


class SFTPTransportSocket {
public:
	SFTPTransportSocket(ParamParser *p,TCPSocket *tcpSock, bool  isActive);

	/* initialize the connection (three way handshake).
	 * Must be called before trying to use the connection.*/
	bool init();

	/* Attempts to send len bytes from location buf. returns number of bytes sent. */
	int transpSend(void *buf, size_t len);

	/* Blocks until some data is ready to be received. Receives up to len bytes into buf.
	 * returns number of bytes received.*/
	int transpRecv(void *buf, size_t len);

	uint32_t getTransportMSS();

	//used to be a checksum, is now a wrapper for a 32bit crc function.
	//start is pointer to beginning of data, len is number of bytes.
	uint32_t checksum(uint16_t * start, uint32_t len);

	//returns x + y, wrapped back to zero at INT_MAX..INT_MAX + 1 = 0
	uint32_t wrappingAdd(uint32_t x, uint32_t y);

	//returns x - y, where x and y wrap to zero after INT_MAX, and
	//x is always guaranteed to be "in front" of or equal to y
	uint32_t wrappingDifference(uint32_t x, uint32_t y);

	void clearStats();
	transport_stats * getTranspStats();
	network_stats * getNetworkStats();
	//returns a string describing this connection.
	string getDescriptorString();

	//closes this transport socket
	void close();

	pthread_mutex_t dataReadyLock;
	pthread_cond_t dataReadyCond;

	ParamParser *paramParser;

	transport_stats stats;

	virtual ~SFTPTransportSocket();

private:
	EmulatedNetworkSocket *netSock;
	TCPSocket *tcpSocket;
	CRC32 crcGenerator;
	bool active;

	bool appCloseRequested;

	enum ConnectionState connState;
	uint32_t retransmissionProtocol;
	uint32_t transportMSS;
	uint32_t transportTimeout;
	uint32_t transportSendBufferSize;
	uint32_t transportReceiveBufferSize;

	uint32_t currBytesInReceiveBuffer;
	uint32_t currBytesInSendBuffer;
	uint32_t currBytesAwaitingAck;

	uint32_t remoteReceiveBufferSize;

	tcp_seq initialSequenceNum;

	tcp_seq sendWindowBase;
	tcp_seq remoteWindowMax;
	tcp_seq nextSendSeqNum;

	//localWindowBase is actually our localWindowBase--in GBN, it is
	//the last ack sent, but it isnt necessarily for SRP
	tcp_seq localWindowBase;
	tcp_seq localWindowMax;

	pthread_t transpControlThread;
	bool resending;
	bool nacked;
	uint32_t nackNum;
	bool windowUpdated;

	//only used for stats description not actual operation
	uint32_t remoteWinSize;

	queue<queue_entry *> fromAppDataQueue;
	queue<queue_entry *> toAppDataQueue;

	list<queue_entry *> awaitingAcks;
	list<queue_entry *> outOfOrderPackets;

	//these are set during init phase based on whether GBN or selective repeat is used
	void (SFTPTransportSocket::*handleAckRcv)(queue_entry *);
	void (SFTPTransportSocket::*handleDataRcv)(queue_entry *);


	/*Initializes a connection from active side. Returns false on failure.*/
	bool activeInit();

	/*Initializes a connection from passive side. Returns false on failure.*/
	bool passiveInit();

	//runs in separate thread to allow receiving of data from network layer
	static void * controlLoop(void *p);

	/* Sends to the network the packet at the head of the queue of packets
	 * from the app layer. Blocks if that queue is empty (until it isnt empty..) */
	void networkSend();

	/* receives the packet at the head of queue from network. Blocks if queue of
	 * packets from network is empty. Takes appropriate action based on whether
	 * the packet is a data packet or an ACK. */
	void networkRecv();

	void resendTimedOutPackets();

	void gbnAckRcv(queue_entry *qe);
	void gbnDataRcv(queue_entry *qe);

	void srpAckRcv(queue_entry *qe);
	void srpDataRcv(queue_entry *qe);

	void sendAck(tcp_seq ackNum);

	void finish();

	//caused by network failure
	void forcedFinish();


	/* returns a bit vector indicating which event(s) have occured. event types
	 * are APP_DATA, NETWORK_DATA, APP_CLOSE_REQUESTED. A return value of zero
	 * indicates a timeout.*/
	uint16_t waitForEvent(uint32_t timeout);

	void generateInitialSeqNum();

	void setParams();

	bool isInSendWindow(tcp_seq ackNum);
	//returns true if the specified sequence num is within our local receive window
	bool isInLocalWindow(tcp_seq seqNum);

};

#endif /* SFTPTRANSPORTSOCKET_H_ */
